//==============================================================================
//
//         DTextScreen - the text screen wrapper class in the ofc-library
//
//               Copyright (C) 2005  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-09-06 19:24:32 $ $Revision: 1.18 $
//
//==============================================================================

#include "ofc/DTextScreen.h"
#include "ofc/DAtExit.h"

#include <stdlib.h>
#include <stdio.h>

#ifdef WIN32
# include <windows.h>
#else
# include <ncurses.h>
#endif


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DColor.h"

#include "ofc/DScreenHandler.h"
#include "ofc/DDrawable.h"

#ifdef WIN32
# define HAVE_DTEXTSCREEN (1)
#else
# if defined(HAVE_NCURSES_H) && defined(HAVE_LIBNCURSES)
#  define HAVE_DTEXTSCREEN (1)
# endif
#endif



#ifdef HAVE_DTEXTSCREEN

//
// The DTextDrawable class implements a class with the shared methods between
// the text screen class and the text surface class. It contains the methods
// for drawing, clipping, color.. The x and y positions start from zero, so the
// screen size is 0..maxX and 0..maxY (inclusive).
// ToDo: crossings between drawn lines, font class, drawing boxes, circles..
// 

@interface DTextDrawable : Object <DDrawable>
{
@protected
  unsigned             _maxX;        // the right x-position of the drawable
  unsigned             _maxY;        // the bottom y-position of the drawable
  unsigned             _attribute;   // the current color attributes

@private
  unsigned             _clipMinX;    // the left x-position for the clip area
  unsigned             _clipMaxX;    // the right x-position for the clip area
  unsigned             _clipMinY;    // the top y-position for the clip area
  unsigned             _clipMaxY;    // the bottom y-position for the clip area
  unsigned             _cursorX;     // the x-position of the cursor
  unsigned             _cursorY;     // the y-position of the cursor
  BOOL                 _drawing;     // is drawing active ?
  DColor              *_fgc;         // the foreground color
  DColor              *_bgc;         // the background color
  
  int                  _lineType;    // the last line type
}

#endif
#endif


#ifdef HAVE_DTEXTSCREEN

#if _PRIVATE_

#ifdef WIN32
static HANDLE  _hOutput   = INVALID_HANDLE_VALUE;
static HANDLE  _hInput    = INVALID_HANDLE_VALUE;
#else
static WINDOW *_screen    = NULL;
static int     _nextPair  = 1;
#endif
static BOOL    _hasColors = NO;


#ifdef WIN32
//
// Convert the text colors to WIN32 attributes
// 
// @param fgc         the foreground color
// @param bgc         the background color
// @param attributes  the WIN32 attributes
// 
// @return success
// 
static BOOL _color2attributes(int *attributes, DColor *fgc, DColor *bgc)
{
  BOOL  ok = NO;
  
  if ((_hOutput != INVALID_HANDLE_VALUE) && (_hasColors))
  {
    *attributes = 0;
    
    switch([fgc textColor])
    {
      case DCLR_BLACK:   break;
      case DCLR_RED:     *attributes = FOREGROUND_RED;   break;
      case DCLR_GREEN:   *attributes = FOREGROUND_GREEN; break;
      case DCLR_YELLOW:  *attributes = FOREGROUND_GREEN|FOREGROUND_RED; break;
      case DCLR_BLUE:    *attributes = FOREGROUND_BLUE;  break;
      case DCLR_MAGENTA: *attributes = FOREGROUND_BLUE|FOREGROUND_RED; break;
      case DCLR_CYAN:    *attributes = FOREGROUND_BLUE|FOREGROUND_GREEN; break;
      case DCLR_WHITE:
      default:           *attributes = FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE; break;
    }
    
    switch([bgc textColor])
    {
      case DCLR_BLACK:   break;
      case DCLR_RED:     *attributes |= BACKGROUND_RED; break;
      case DCLR_GREEN:   *attributes |= BACKGROUND_GREEN; break;
      case DCLR_YELLOW:  *attributes |= BACKGROUND_GREEN|BACKGROUND_RED; break;
      case DCLR_BLUE:    *attributes |= BACKGROUND_BLUE; break;
      case DCLR_MAGENTA: *attributes |= BACKGROUND_BLUE|BACKGROUND_RED; break;
      case DCLR_CYAN:    *attributes |= BACKGROUND_GREEN|BACKGROUND_BLUE; break;
      case DCLR_WHITE:   
      default:           *attributes |= BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE; break;
    }
    
    ok = YES;
  }
  
  return ok;
}
#else
//
// Convert the text color to the curses color
// 
// @param color     the text color
// 
// @return the curses color
// 
static short _color2curses(int color)
{
  switch (color)
  {
    case DCLR_BLACK:   return COLOR_BLACK;
    case DCLR_RED:     return COLOR_RED;
    case DCLR_GREEN:   return COLOR_GREEN;
    case DCLR_YELLOW:  return COLOR_YELLOW;
    case DCLR_BLUE:    return COLOR_BLUE;
    case DCLR_MAGENTA: return COLOR_MAGENTA;
    case DCLR_CYAN:    return COLOR_CYAN;
    case DCLR_WHITE:
    default:           return COLOR_WHITE;
  }
}

static BOOL _color2attributes(int *attributes, DColor *fgc, DColor *bgc)
{
  BOOL  ok = NO;
  
  if ((_screen != NULL) && (_hasColors))
  {
    short newfgc = _color2curses([fgc textColor]);
    short newbgc = _color2curses([bgc textColor]);
    short oldfgc;
    short oldbgc;
    int   index  = 0;
    int   color  = -1;
      
    while ((index < _nextPair) && (color == -1))
    {
      pair_content(index, &oldfgc, &oldbgc);
    
      if ((newfgc == oldfgc) && (newbgc == oldbgc))
      {
        color = index;
      }
    
      index++;
    }
      
    if ((color == -1) && (_nextPair < COLOR_PAIRS))
    {
      color = index;
    
      init_pair(color, newfgc, newbgc);
    
      _nextPair++;
    }
      
    if (color != -1)
    {
      *attributes = COLOR_PAIR(color);
      
      ok = YES;
    }
  }
  
  return ok;
}
#endif
#endif


@implementation DTextDrawable

//
// Initialise to an text drawable object
//
// @return the object
//
- (DTextDrawable *) init
{
  [super init];
  
  _clipMinX    = 0;
  _clipMaxX    = 0;
  _clipMinY    = 0;
  _clipMaxY    = 0;
  
  _cursorX     = 0;
  _cursorY     = 0;
  
  _maxX        = 0;
  _maxY        = 0;

  _fgc         = [DColor new];
  _bgc         = [DColor new];
  _attribute   = 0;

  _drawing     = NO;

  _lineType    = DDW_LINE_SOLID;

  return self;
}


//// Copy related methods

//
// Do a shallow copy of the text drawable object
//
// @return the object
//
- shallowCopy
{
  DTextDrawable *copy = [super shallowCopy];
  
  copy->_fgc = [_fgc copy];
  copy->_bgc = [_bgc copy];
  
  return copy;
}


//// Deconstructor

//
// Free the text screen
// 
// @return the object
// 
- free
{
  [_fgc free];
  [_bgc free];

  return [super free];
}


//// Cursor methods

//
// Return the x position of the cursor
// 
// @return the x position
// 
- (unsigned) cursorX
{
  return _cursorX;
}

//
// Return the y position of the cursor
// 
// @return the y position
//
- (unsigned) cursorY
{
  return _cursorY;
}

//
// Set the cursor to a position
// 
// @param x     the x-position
// @param y     the y-position
// 
// @return success
// 
- (BOOL) cursor :(unsigned) x :(unsigned) y
{
  BOOL ok = NO;
  
  if (x > _maxX)
  {
    WARNING(DW_INVALID_ARG, "x");
  }
  else if (y > _maxY)
  {
    WARNING(DW_INVALID_ARG, "y");
  }
  else
  {
    _cursorX = x;
    _cursorY = y;
    
    ok = YES;
  }
  
  return ok;
}

//
// Set the visibility of the cursor
// 
// @param state   the cursor state (DDW_CURSOR..)
// 
// @return the previous state of the cursor
// 
- (int) cursor :(int) state
{
  WARNING(DW_METHOD_NOT_IMPL, "cursor");
  
  return DDW_CURSOR_OFF;
}

//
// Set the visibility of the mouse pointer
// 
// @param state   the pointer state (DDW_POINTER..)
// 
// @return the previous state of the pointer
// 
- (int) pointer :(int) state
{
  WARNING(DW_METHOD_NOT_IMPL, "pointer");
  
  return DDW_POINTER_OFF;
}

//
// Return the last error
// 
// @return the last error (0)
// 
- (int) error
{
  return 0;
}

//
// Tell the current position in the writeable
//
// @return the current position
//
- (unsigned long) tell
{
  return _cursorX + (_maxX+1) * _cursorY;
}

//
// Move the current position to a location
//
// @param offset   the offset from the origin (in bytes)
// @param origin   the origin for the offset (0=begin, 1=current, 2=end)
//
// @return success
//

- (BOOL) seek :(unsigned long) offset :(int) origin
{
  BOOL ok = YES;
  
  unsigned long position = 0L;
  unsigned long end      = (_maxX+1) * (_maxY+1) - 1;
  
  switch (origin)
  {
   case 0:
     position = offset;
     break;
    
   case 1:
     position = [self tell] + offset;
     break;
    
   case 2:
     if (offset <= end)
     {
       position = end - offset;
     }
     else
       ok = NO;
     break;

   default:
     WARNING(DW_INVALID_ARG, "origin");
     ok = NO;
     break;
  }
  
  if (ok)
  {
    if (position <= end)
    {
      ok = [self cursor :(position % (_maxX+1)) :(position / (_maxX+1))];
    }
    else
      ok = NO;
  }

  return ok;
}

//
// Skip a number of positions
//
// @param offset   the number of bytes to skip
//
// @return success
//
- (BOOL) skip :(unsigned long) offset
{
  BOOL ok = NO;
  
  unsigned long position = [self tell] + offset;
  
  if (position < ((_maxX+1) * (_maxY+1) - 1))
  {
    [self cursor :(position % (_maxX+1)) :(position / (_maxX+1))];
    
    ok = YES;
  }

  return ok;
}


//// Surface methods

//
// Return the maximum x-position
// 
// @return the maximum x-position
//
- (unsigned) maxX
{
  return _maxX;
}

//
// Return the number of lines
// 
// @return the number of lines
// 
- (unsigned) lines
{
  return _maxY+1;
}

//
// Return the maximum y-position
// 
// @return the maximum y-position
// 
- (unsigned) maxY
{
  return _maxY;
}

//
// Return the number of columns
// 
// @return the number of columns
// 
- (unsigned) columns
{
  return _maxX+1;
}

//
// Set the dimensions of the drawable
// 
// @param columns the number of columns (> 0)
// @param lines   the number of lines (> 0)
// 
// @return the object
// 
- (DTextDrawable *) drawable :(unsigned) columns :(unsigned) lines
{
  if (columns == 0) 
  {
    WARNING(DW_INVALID_ARG, "columns");
  }
  else if (lines == 0)
  {
    WARNING(DW_INVALID_ARG, "lines");
  }
  else
  {
    _maxX = columns-1;
    _maxY = lines-1;
    
    if (_clipMaxX > _maxX) _clipMaxX = _maxX;
    if (_clipMaxY > _maxY) _clipMaxY = _maxY;
  }
  
  return self;
}

//
// Check if a cursor position is valid (within drawable)
// 
// @param x     the x-position
// @param y     the y-position
// 
// @return is it ?
// 
- (BOOL) isValid :(unsigned) x :(unsigned) y
{
  return ((x <= _maxX) && (y <= _maxY));
}


//// Clipping methods

//
// Set a clipping area
// 
// @param minX    the left x-position of the clip area
// @param maxX    the right x-position of the clip area
// @param minY    the top y-position of the clip area
// @param maxY    the bottom y-position of the clip area
// 
// @return success
// 
- (BOOL) clip :(unsigned) minX :(unsigned) minY :(unsigned) maxX :(unsigned) maxY
{
  BOOL ok = NO;

  if (minX > maxX)
  {
    WARNING(DW_INVALID_ARG, "minX");
  }
  else if (minY > maxY)
  {
    WARNING(DW_INVALID_ARG, "minY");
  }
  else
  {
    _clipMinX = minX;
    _clipMinY = minY;
    _clipMaxX = maxX;
    _clipMaxY = maxY;
    
    if (_clipMaxX > _maxX) _clipMaxX = _maxX;
    if (_clipMaxY > _maxY) _clipMaxY = _maxY;
    
    ok = YES;
  }
  
  return ok;
}

//
// Reset the clipping area to the full surface
// 
// @return success
// 
- (BOOL) clip
{
  _clipMinX = 0;
  _clipMaxX = _maxX;
  _clipMinY = 0;
  _clipMaxY = _maxY;
  
  return YES;
}

//
// Return the top position of the clip area
// 
// @return the top y-position
// 
- (unsigned) clipMinY
{
  return _clipMinY;
}

//
// Return the bottom position of the clip area
// 
// @return the bottom y-position
// 
- (unsigned) clipMaxY
{
  return _clipMaxY;
}

//
// Return the left position of the clip area
// 
// @return the left x-position
// 
- (unsigned) clipMinX
{
  return _clipMinX;
}

//
// Return the right position of the clip area
// 
// @return the right x-position
// 
- (unsigned) clipMaxX
{
  return _clipMaxX;
}


//// Color methods

//
// Set the fore- and background color
// 
// @param fgc     the foreground color
// @param bgc     the background color
// 
// @return success
// 
- (BOOL) color :(DColor *) fgc :(DColor *) bgc
{
  BOOL ok = NO;

  if (fgc == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "fgc");
  }
  else if (bgc == nil)
  {
    WARNING(DW_NIL_NOT_ALLOWED, "bgc");
  }
  else
  {
    int attributes = 0;
    
    if (_color2attributes(&attributes, fgc, bgc))
    {
      _attribute = attributes;
      
      ok = YES;
    }
  }

  return ok;
}

//
// Set the foreground color
// 
// @param fgc     the foreground color
// 
// @return success
// 
- (BOOL) foregroundColor :(DColor *) fgc
{
  return [self color :fgc :_bgc];
}

//
// Get the foreground color
// 
// @return a reference to the foreground color
// 
- (DColor *) foregroundColor
{
  return _fgc;
}

//
// Set the background color
// 
// @param bgc     the background color
// 
// @return success
// 
- (BOOL) backgroundColor :(DColor *) bgc
{
  return [self color :_fgc :bgc];
}

//
// Get the background color
// 
// @return a reference to the background color
// 
- (DColor *) backgroundColor
{
  return _bgc;
}


//// Drawing control methods

//
// Start drawing on the screen
// 
// @return success
// 
- (BOOL) startDrawing
{
  _drawing = YES;
  
  return [self clip];
}

//
// Start drawing in a clipped area on the screen
// 
// @param minX      the left x-position of the clipping area
// @param maxX      the right x-position of the clipping area
// @param minY      the top y-position of the clipping area
// @param maxY      the bottom y-position of the clipping area
// 
// @return success
//
- (BOOL) startDrawing :(unsigned) minX :(unsigned) minY :(unsigned) maxX :(unsigned) maxY
{
  BOOL ok = [self startDrawing];
  
  if (ok)
  {
    ok = [self clip :minX :minY :maxX :maxY];
  }
  
  return ok;
}

//
// Stop drawing on the screen
// 
// @return the screen object
// 
- (DTextDrawable *) stopDrawing
{
  _drawing = NO;
  
  return self;
}

//
// Check if the screen is in drawing mode
// 
// @return is it ?
// 
- (BOOL) isDrawing
{
  return _drawing;
}


//// Drawing methods

//
// Clear the drawable
// 
// @return success
// 
- (BOOL) clear
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else
  {
    unsigned x;
    unsigned y;

    ok = YES;
    for (y = _clipMinY; y <= _clipMaxY; y++)
    {
      for (x = _clipMinX; x <= _clipMaxX; x++)
      {
        ok &= [self _putChar :x :y :' ' :_attribute];
      }
    }
  }
  
  return ok;
}

//
// Write a text string on the screen on the current cursor position
// 
// @param text    the text string
// 
// @return success
// 
- (BOOL) writeText :(const char *) text
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if (text == NULL)
  {
    WARNING(DW_INVALID_ARG, "text");
  }
  else
  {
    while (*text != EOS)
    {
      if ((_cursorX >= _clipMinX) && (_cursorX <= _clipMaxX) &&
          (_cursorY >= _clipMinY) && (_cursorY <= _clipMaxY))
      {
        [self _putChar :_cursorX :_cursorY :*text :_attribute];
      }
      
      text++;
      _cursorX++;
    }
    
    ok = YES;
  }
  
  return ok;
}

//
// Write a character on the screen on the current cursor position
// 
// @param ch      the character
// 
// @return success
// 
- (BOOL) writeChar :(char) ch
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else
  {
    if ((_cursorX >= _clipMinX) && (_cursorX <= _clipMaxX) &&
        (_cursorY >= _clipMinY) && (_cursorY <= _clipMaxY))
    {
      [self _putChar :_cursorX :_cursorY :ch :_attribute];
    }
      
    _cursorX++;
    
    ok = YES;
  }
  
  return ok;
}

//
// Write a text line on the screen on the current cursor position
// 
// @param text    the text string
// 
// @return success
// 
- (BOOL) writeLine :(const char *) text
{
  BOOL ok = [self writeText :text];
  
  if (ok)
  {
    _cursorX = 0;
    
    _cursorY++;
  }
  
  return ok;
}

//
// Write a text string on the screen
// 
// @param startX  the start x position
// @param startY  the start y position
// @param text    the text string
// 
// @return success
// 
- (BOOL) writeText :(unsigned) startX :(unsigned) startY :(const char *) text
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if (text == NULL)
  {
    WARNING(DW_INVALID_ARG, "text");
  }
  else if ([self cursor :startX :startY])
  {
    while (*text != EOS)
    {
      if ((_cursorX >= _clipMinX) && (_cursorX <= _clipMaxX) &&
          (_cursorY >= _clipMinY) && (_cursorY <= _clipMaxY))
      {
        [self _putChar :_cursorX :_cursorY :*text :_attribute];
      }
      
      text++;
      _cursorX++;
    }
    
    ok = YES;
  }
  
  return ok;
}

//
// Write a character on the screen
// 
// @param startX  the start x position
// @param startY  the start y position
//
// @param ch      the character
// 
// @return success
// 
- (BOOL) writeChar :(unsigned) startX :(unsigned) startY :(char) ch
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if ([self cursor :startX :startY])
  {
    if ((_cursorX >= _clipMinX) && (_cursorX <= _clipMaxX) &&
        (_cursorY >= _clipMinY) && (_cursorY <= _clipMaxY))
    {
      [self _putChar :startX :startY :ch :_attribute];
    }
      
    _cursorX++;
    
    ok = YES;
  }
  
  return ok;
}

//
// Read a character from the drawable
// 
// @param startX  the start x position
// @param startY  the start y position
// 
// @return the character (or -1)
// 
- (int) readChar :(unsigned) startX :(unsigned) startY
{
  int ch = -1;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if ([self cursor :startX :startY])
  {
    unsigned attr = 0;
    
    if (![self _getChar :startX :startY :&ch :&attr])
    {
      ch = -1;
    }
  }
  
  return ch;
}


#if _PRIVATE_

//
// Draw horizontal line
// 
// @param self    the text drawable object
// @param ex      the end x-position
// 
// @return success
// 
static BOOL _drawHLine(DTextDrawable *self, unsigned ex)
{
  BOOL ok = YES;

  unsigned sx         = self->_cursorX;
  unsigned sy         = self->_cursorY;
  unsigned clipMinX   = self->_clipMinX;
  unsigned clipMaxX   = self->_clipMaxX;
  unsigned attributes = self->_attribute;
  
#ifdef WIN32
  int  ch1      = 169; // '-';
  int  ch2      = 169; // '-';
#else 
  int  ch1      = ACS_HLINE;
  int  ch2      = ACS_HLINE;
#endif
  int  diff     = (sx > ex ? -1 : 1);
  int  length   = (sx > ex ? (sx - ex + 1) : (ex - sx + 1));
  
  BOOL on     = YES;
  
  if (self->_lineType == DDW_LINE_DOTTED)
  {
    ch1 = ch2 = '.';
  }
  else if (self->_lineType == DDW_LINE_DASHED)
  {
    ch2 = ' ';
  }

  while (length > 0)
  {
    if ((sx >= clipMinX) && (sx <= clipMaxX))
    {
      // ToDo: crossings
      ok &= [self _putChar :sx :sy :(on ? ch1 : ch2) :attributes];
    }
    
    self->_cursorX = sx;
    
    on = !on;
    
    sx += diff;
    length--;
  }
  
  return ok;
}

//
// Draw a vertical line
// 
// @param self    the text drawable object
// @param ey      the end y-position
// 
// @return success
// 
static BOOL _drawVLine(DTextDrawable *self, unsigned ey)
{
  BOOL     ok       = YES;
  
  unsigned sx         = self->_cursorX;
  unsigned sy         = self->_cursorY;
  unsigned clipMinY   = self->_clipMinY;
  unsigned clipMaxY   = self->_clipMaxY;
  unsigned attributes = self->_attribute;
  
#ifdef WIN32
  int      ch1      = 179; // '|';
  int      ch2      = 179; // '|';
#else
  int      ch1      = ACS_VLINE;
  int      ch2      = ACS_VLINE;
#endif
  int      diff     = (sy < ey ? 1 : -1);
  int      length   = (sy > ey ? (sy - ey + 1) : (ey - sy + 1));
  
  BOOL on     = YES;

  if (self->_lineType == DDW_LINE_DOTTED)
  {
    ch1 = ch2 = '.';
  }
  else if (self->_lineType == DDW_LINE_DASHED)
  {
    ch2 = ' ';
  }

  while (length > 0)
  {
    if ((sy >= clipMinY) && (sy <= clipMaxY))
    {
      // ToDo: crossings
      ok &= [self _putChar :sx :sy :(on ? ch1 : ch2) :attributes];
    }
    
    self->_cursorY = sy;
    
    on = !on;
    
    sy += diff;
    length--;
  }

  return ok;
}

//
// Draw a line
// 
// @param self    the text drawable object
// @param ex      the end x-position
// @param ey      the end y-position
// 
// @return success
// 
static BOOL _drawLine(DTextDrawable *self, unsigned ex, unsigned ey)
{
  unsigned sx         = self->_cursorX;
  unsigned sy         = self->_cursorY;
  unsigned clipMinX   = self->_clipMinX;
  unsigned clipMaxX   = self->_clipMaxX;
  unsigned clipMinY   = self->_clipMinY;
  unsigned clipMaxY   = self->_clipMaxY;
  unsigned attributes = self->_attribute;
  
  double   posX     = (double) sx;
  double   posY     = (double) sy;
    
  int      dX       = (ex - sx);
  int      dY       = (ey - sy);
  int      dMax     = (abs(dX) > abs(dY) ? abs(dX) : abs(dY));
    
  double   diffX    = (double) dX / (double) (abs(dY));
  double   diffY    = (double) dY / (double) (abs(dX));
    
#ifdef WIN32
  int      ch1      =  197; // '+';
  int      ch2      =  197; // '+'; 
#else
  int      ch1      = ACS_PLUS;
  int      ch2      = ACS_PLUS;
#endif
  BOOL     on       = YES;
  BOOL     ok       = YES;
  
  
  if (diffX < -1.0) diffX = -1.0;
  if (diffX >  1.0) diffX =  1.0;
  if (diffY < -1.0) diffY = -1.0;
  if (diffY >  1.0) diffY =  1.0;
    
  if (self->_lineType == DDW_LINE_DOTTED)
  {
    ch1 = ch2 = '.';
  }
  else if (self->_lineType == DDW_LINE_DASHED)
  {
    ch2 = ' ';
  }

  while (dMax >= 0)
  {
    if ((sx >= clipMinX) && (sx <= clipMaxX) &&
        (sy >= clipMinY) && (sy <= clipMaxY))
    {
      // ToDo: crossings
      ok &= [self _putChar :sx :sy :(on ? ch1 : ch2) :attributes];
    }
    
    self->_cursorX = sx;
    self->_cursorY = sy;
    
    on = !on;
    
    posX += diffX;
    posY += diffY;
      
    sx = (int) (posX + 0.5);
    sy = (int) (posY + 0.5);
    
    dMax--;
  }
  
  return ok;
}
#endif


//
// Draw a (horizontal) line on the drawable
//
// @param startX    the x position for the start of the line
// @param endX      the x position for the end of the line
// @param startY    the y position for the line
// @param lineType  the type of the line DDW_LINE_...
//
// @return success
//
- (BOOL) drawHLine :(unsigned) startX :(unsigned) startY :(unsigned) endX :(int) lineType
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if ((lineType < DDW_LINE_SOLID) || (lineType > DDW_LINE_DOTTED))
  {
    WARNING(DW_INVALID_ARG, "lineType");
  }
  else if (![self isValid :startX :endX])
  {
    WARNING(DW_INVALID_ARG, "startX/endX");
  }
  else if ([self cursor :startX :startY]) 
  {
    _lineType  = lineType;
    
    ok = _drawHLine(self, endX);
  }
  
  return ok;
}

//
// Draw a horizontal line with the previous line width and type settings
//
// @param startX    the x position for the start of the line
// @param endX      the x position for the end of the line
// @param startY    the y position for the line
//
// @return success
//
- (BOOL) drawHLine :(unsigned) startX :(unsigned) startY :(unsigned) endX 
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if (![self isValid :endX :startY])
  {
    WARNING(DW_INVALID_ARG, "startX/endX");
  }
  else if ([self cursor :startX :startY])
  {
    ok = _drawHLine(self, endX);
  }
  
  return ok;
}

//
// Draw a (horizontal) line on the drawable starting from the current position and
// with the previous line width and type settings
//
// @param endX      the x position for the end of the line
//
// @return success
//
- (BOOL) drawHLine : (unsigned) endX 
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if (![self isValid :endX :_cursorY])
  {
    WARNING(DW_INVALID_ARG, "endX");
  }
  else
  {
    ok = _drawHLine(self, endX);
  }
  
  return ok;
}

//
// Draw a vertical line on the screen
// 
// @param startX      the x position for the line
// @param startY      the y position for the line
// @param endY        the y position for the end of the line
// @param lineType    the type of the line (DDW_LINE_...)
// 
// @return success
// 
- (BOOL) drawVLine :(unsigned) startX :(unsigned) startY :(unsigned) endY :(int) lineType
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if ((lineType < DDW_LINE_SOLID) || (lineType > DDW_LINE_DOTTED))
  {
    WARNING(DW_INVALID_ARG, "lineType");
  }
  else if (![self isValid :startX :endY])
  {
    WARNING(DW_INVALID_ARG, "startX/endY");
  }
  else if ([self cursor :startX :startY]);
  {
    _lineType  = lineType;
    
    ok = _drawVLine(self, endY);
  }
  
  return ok;
}

//
// Draw a vertical line with the current line type and width settings
// 
// @param startX      the x position for the line
// @param startY      the y position for the line
// @param endY        the y position for the end of the line
// 
// @return success
// 
- (BOOL) drawVLine :(unsigned) startX :(unsigned) startY :(unsigned) endY
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if (![self isValid :startX :endY])
  {
    WARNING(DW_INVALID_ARG, "startX/endY");
  }
  else if ([self cursor :startX :startY])
  {
    ok = _drawVLine(self, endY);
  }
  
  return ok;
}

//
// Draw a (vertical) line on the drawable starting from the current position and
// with the current line width and type settings
//
// @param endY      the y position for the end of the line
//
// @return success
//
- (BOOL) drawVLine :(unsigned) endY
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if (![self isValid :_cursorX :endY])
  {
    WARNING(DW_INVALID_ARG, "endY");
  }
  else
  {
    ok = _drawVLine(self, endY);
  }
  
  return ok;
}

//
// Draw a line on the screen
//
// @param startX      the x position for the start of the line 
// @param endX        the x position for the end of the line
// @param startY      the y position for the start of the line
// @param endY        the y position for the end of the line
// @param lineType    the type of the line (DDW_LINE_...)
// 
// @return success
// 
- (BOOL) drawLine :(unsigned) startX :(unsigned) startY :(unsigned) endX :(unsigned) endY :(int) lineType
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if ((lineType < DDW_LINE_SOLID) || (lineType > DDW_LINE_DOTTED))
  {
    WARNING(DW_INVALID_ARG, "lineType");
  }
  else if (![self isValid :endX :endY])
  {
    WARNING(DW_INVALID_ARG, "endX/endY");
  }
  else if ([self cursor :startX :startY]);
  {
    _lineType  = lineType;
    
    if (startX == endX)
    {
      ok = _drawVLine(self, endY);
    }
    else if (startY == endY)
    {
      ok = _drawHLine(self, endX);
    }
    else
    {
      ok = _drawLine(self, endX, endY);
    }
  }
  
  return ok;
}
  
//
// Draw a line with the current line type and width settings
//
// @param startX  the x position for the start of the line 
// @param endX    the x position for the end of the line
// @param startY  the y position for the start of the line
// @param endY    the y position for the end of the line
// 
// @return success
// 
- (BOOL) drawLine :(unsigned) startX :(unsigned) startY :(unsigned) endX :(unsigned) endY
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if (![self isValid :endX :endY])
  {
    WARNING(DW_INVALID_ARG, "endX/endY");
  }
  else if ([self cursor :startX :startY])
  {
    if (startX == endX)
    {
      ok = _drawVLine(self, endY);
    }
    else if (startY == endY)
    {
      ok = _drawHLine(self, endX);
    }
    else
    {
      ok = _drawLine(self, endX, endY);
    }
  }
  
  return ok;
}

//
// Draw a line on the drawable starting from the current position and
// with the current line width and type settings
//
// @param endX      the x-position for the end of the line
// @param endY      the y-position for the end of the line
//
// @return success
//
- (BOOL) drawLine :(unsigned) endX :(unsigned) endY
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if (![self isValid :endX :endY])
  {
    WARNING(DW_INVALID_ARG, "endX/endY");
  }
  else  if (_cursorX == endX)
  {
    ok = _drawVLine(self, endY);
  }
  else if (_cursorY == endY)
  {
    ok = _drawHLine(self, endX);
  }
  else
  {
    ok = _drawLine(self, endX, endY);
  }
  
  return ok;
}

//
// Draw a point on the drawable
// 
// @param startX   the x-position
// @param startY   the y-position
// 
// @return success
// 
- (BOOL) drawPoint :(unsigned) startX :(unsigned) startY
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if ([self cursor :startX :startY])
  {
    if ((startX >= _clipMinX) && (startX <= _clipMaxX) &&
        (startY >= _clipMinY) && (startY <= _clipMaxY))
    {
      [self _putChar :startX :startY :'.' :_attribute];
    }
      
    _cursorX++;
    
    ok = YES;
  }
  
  return ok;
}

//
// Draw a point on the current position
// 
// @return success
// 
- (BOOL) drawPoint
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else
  {
    if ((_cursorX >= _clipMinX) && (_cursorY <= _clipMaxX) &&
        (_cursorY >= _clipMinY) && (_cursorY <= _clipMaxY))
    {
      [self _putChar :_cursorX :_cursorY :'.' :_attribute];
    }
      
    _cursorX++;
    
    ok = YES;
  }
  
  return ok;
}

//
// Blit another text drawable on this text drawable
// 
// @param startX    the start x-position where the info is placed on this drawable
// @param startY    the start y-position where the info is placed on this drawable
// @param other     the other drawable
// @param oStartX   the start x-position on the other drawable
// @param oEndX     the end x-position on the other drawable
// @param oStartY   the start y-position on the other drawable
// @param oEndY     the end y-position on the other drawable
// 
// @return success
// 
- (BOOL) blit :(unsigned) startX :(unsigned) startY 
              :(DTextDrawable *) other :(unsigned) oStartX :(unsigned) oStartY :(unsigned) oEndX :(unsigned) oEndY
{
  BOOL ok = NO;
  
  if (!_drawing)
  {
    WARNING(DW_INVALID_STATE, "startDrawing");
  }
  else if (![other isValid :oStartX :oStartY])
  {
    WARNING(DW_INVALID_ARG, "oStartX/oStartY");
  }
  else if (![other isValid :oEndX :oEndY])
  {
    WARNING(DW_INVALID_ARG, "oEndX/oEndY");
  }
  else if (![self isValid :startX :startY])
  {
    WARNING(DW_INVALID_ARG, "startX/startY");
  }
  else
  {
    int x,y;
    
    int xCnt = (oStartX > oEndX ? oStartX - oEndX + 1 : oEndX - oStartX + 1);
    int yCnt = (oStartY > oEndY ? oStartY - oEndY + 1 : oEndY - oStartY + 1);
    
    ok = YES;
    
    for (y = 0; y < yCnt; y++)
    {
      for (x = 0; x < xCnt; x++)
      {
        int      ch   = 0;
        unsigned attr = 0;
        
        if ([other _getChar :oStartX+x :oStartY+y :&ch :&attr])
        {
          if (((startX+x) >= _clipMinX) && ((startX+x) <= _clipMaxX) &&
              ((startY+y) >= _clipMinY) && ((startY+y) <= _clipMaxY))
          {
            ok &= [self _putChar :startX+x :startY+y :ch :attr];
          }
        }
      }
    }
  }
  
  return ok;
}


//// Private methods

//
// Put a character on the screen (internal use, to be overridden)
//
// @param x       the x-position
// @param y       the y-position 
// @param ch      the character
// @param attr    the attributes
// 
// @return success
// 
- (BOOL) _putChar :(unsigned) x :(unsigned) y :(int) ch :(unsigned) attr
{
  return NO;
}

//
// Read a character from the screen (internal use, to be overridden)
// 
// @param x       the x-position
// @param y       the y-position
// @param ch      the read character
// @param attr    the read attributes
// 
// @return success
// 
- (BOOL) _getChar :(unsigned) x :(unsigned) y :(int *) ch :(unsigned *) attr
{
  return NO;
}

@end

#endif
  
  
  
  
  
#if _INTERFACE_

#ifdef HAVE_DTEXTSCREEN

//
// The DTextScreen class implements a class to use the text screen. On unix it
// is a wrapper for the ncurses library. For obvious reasons there can be only
// one text screen object in any application.
// 
// @example
// #include <stdio.h>
// #include "ofc/DTextScreen.h"
// 
// #ifdef HAVE_DTEXTSCREEN 
// @interface MyScreen : DTextScreen <DScreenHandler>  // Class for the screen and its events
// {
//   DColor *fgc;
//   DColor *bgc;
// }
// - (MyScreen *) init;
// - (BOOL) open;
// - (void) draw :(const char *) key;
// - (BOOL) keyPress :(DKey *) key :(int) state;      // Callbacks
// - (BOOL) mouseMove :(unsigned) x :(unsigned) y;
// - (BOOL) mouseButtonPress :(DKey *) button :(int) state;
// - (BOOL) screenResize :(unsigned) maxX :(unsigned) maxY;
// - (BOOL) screenFocus :(BOOL) focus;
// - (BOOL) screenRedraw :(unsigned) minX :(unsigned) maxX :(unsigned) minY :(unsigned) maxY;
// @end
// 
// @implementation MyScreen
// 
// - (MyScreen *) init
// {
//   [super init];
//   
//   fgc = [DColor new];
//   bgc = [DColor new];
// 
//   return self;
// }
// 
// - (BOOL) open                          // Open the screen with ..
// {
//   BOOL ok = NO;
// 
//   [fgc textColor :DCLR_YELLOW];        // .. foreground and background color
//   [bgc textColor :DCLR_BLUE];
//   
//   ok = [self open :fgc :bgc];
// 
//   if (ok)
//   {
//     [self screenHandler :self];        // Object is handling its events itself
//   }
// 
//   return ok;
// }
// 
// - (void) draw :(const char *) key      // Screen drawing
// {
//   int maxX = [self maxX];              // Dimensions of screen
//   int maxY = [self maxY];
// 
//   char hello[] = "Hello World";
//   char quit[]  = "Press q to quit";
// 
//   [self startDrawing];                 // Start drawing
//   [self clear];
//   [self drawHLine :1 :1 :maxX-1];      // Draw a some lines
//   [self drawHLine :maxX-1 :maxY-1 :1];
//                                        // Write some text
//   [self cursor :((maxX - strlen(hello))/2) :((maxY-2)/2)];
//   [self writeText :hello];
// 
//   [self cursor :((maxX - strlen(quit))/2) :((maxY+2)/2)];
//   [self writeText :quit];
// 
//   if (key != NULL)
//   {
//     [self cursor :3 :maxY-3];
//     [self writeText :"Key pressed:"];
//     [self writeText :key];
//   }
//   [self stopDrawing];
// }
//                                        // Callback for pressed key
// - (BOOL) keyPress :(DKey *) key :(int) state
// {
//   if ([key key] != 'q')
//   {
//     DText *info = [key toText];
//     
//     [self draw :[info cstring]];
// 
//     [info free];
//   }
//   
//   return ([key key] != 'q');
// }
//                                        // Callback for moved mouse
// - (BOOL) mouseMove :(unsigned) x :(unsigned) y
// {
//   return YES;
// }
//                                        // Callback for pressed mouse button
// - (BOOL) mouseButtonPress :(DKey *) button :(int) state
// {
//   return YES;
// }
//                                        // Callback for screen resize
// - (BOOL) screenResize :(unsigned) maxX :(unsigned) maxY
// {
//   [self draw :NULL];
// 
//   return YES;
// }
//                                        // Callback for lost and gained screen focus
// - (BOOL) screenFocus :(BOOL) focus
// {
//   [self draw :NULL];
// 
//   return YES;
// }
//                                        // Callback for screen redraw
// - (BOOL) screenRedraw :(unsigned) minX :(unsigned) maxX :(unsigned) minY :(unsigned) maxY
// {
//   [self draw :NULL];
// 
//   return YES;
// }
// 
// @end
// #endif
// 
// int main(int argc, char *argv[])
// {
// #ifdef HAVE_DTEXTSCREEN 
//   MyScreen *screen = [MyScreen new];
// 
//   if ([screen open])                   // Open the screen
//   {
//     [screen draw :NULL];               // Draw the screen
// 
//     [screen waitEvents];               // Wait for and process events
//   }
//   else
//     printf("Could not open the screen..\n");
// 
//   [screen free];                       // Cleanup
// #else
//   printf("DTextScreen is not present in the library.\n");
// #endif
//   return 0;
// }
// 

@interface DTextScreen : DTextDrawable
{
@private
  BOOL                 _isOpen;      // is the text screen open ?
  id <DScreenHandler>  _handler;     // the screen event handler
  DKey                *_key;         // the key
  unsigned long        _mouseMask;   // the previous mouse mask
}

#endif
#endif



#ifdef HAVE_DTEXTSCREEN

@implementation DTextScreen


//// Constructors

//
// Initialise to an unopened text screen object
//
// @return the object
//
- (DTextScreen *) init
{
  [super init];
  
  _isOpen      = NO;
  _key         = [DKey new];
  _handler     = nil;
  _mouseMask   = 0L;
  
  return self;
}

//
// Initialise an opened text screen object with a fore- and background color
// 
// @param fgc     the foreground color
// @param bgc     the background color
//
// @return the object
//
- (DTextScreen *) init :(DColor *) fgc :(DColor *) bgc
{
  [self init];

  [self open :fgc :bgc];
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of the text screen object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");
  
  return [super shallowCopy];
}

  
//// Deconstructor

//
// Free the text screen
// 
// @return the object
// 
- free
{
  [self close];

  _handler = nil;

  [_key free];

  return [super free];
}


//// Screen status methods

//
// Check if the text screen object is open
// 
// @return is it ?
//
- (BOOL) isOpen
{
  return _isOpen;
}

//
// Check if the terminal support colors
// 
// @return support the terminal colors ?
// 
- (BOOL) hasColors
{
  return _hasColors;
}



//// Event handler methods

//
// Set the screen event handler
// 
// @param handler   the (reference to the) screen handler
// 
// @return the previous screen handler
// 
- (id <DScreenHandler>) screenHandler :(id <DScreenHandler>) handler
{
  id <DScreenHandler> tmp = _handler;
  
  _handler = handler;
  
  return tmp;
}

//
// Return the current screen event handler
// 
// @return the (reference to the) screen handler
// 
- (id <DScreenHandler>) screenHandler
{
  return _handler;
}


//// Screen control methods

//
// Open the text screen (only one instance is opened)
//
// @param fgc     the foreground color
// @param bgc     the background color
// 
// @return success
// 
- (BOOL) open :(DColor *) fgc :(DColor *) bgc
{
  BOOL ok = NO;
  
#ifdef WIN32
  if (_hOutput != INVALID_HANDLE_VALUE)
  {
    WARNING(DW_UNKNOWN_WARNING, "screen already open");
  }
  else
  {
    _hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    _hInput  = GetStdHandle(STD_INPUT_HANDLE);
    
    if ((_hOutput != INVALID_HANDLE_VALUE) && (_hInput != INVALID_HANDLE_VALUE))
    {
      CONSOLE_SCREEN_BUFFER_INFO info;
      
      if (!GetConsoleScreenBufferInfo(_hOutput, &info))
      {
        WARNING(DW_UNKNOWN_WARNING, "GetConsoleScreenBufferInfo failed");
      }
      else
      {
        DWORD mode;
        
        int cols = info.srWindow.Right  - info.srWindow.Left + 1;
        int rows = info.srWindow.Bottom - info.srWindow.Top  + 1;
        
        [DAtExit add :self];
      
        _hasColors = YES;
        
        if (GetConsoleMode(_hInput, &mode))
        {
          _mouseMask = mode;
        }
        
        mode = ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT; 
        
        if (!SetConsoleMode(_hInput, mode))
        {
          WARNING(DW_UNKNOWN_WARNING, "SetConsoleMode failed");
        }
        
        [self drawable :cols :rows];
         
        ok = YES;
      }
    }
  }
#else
  if (_screen != NULL)
  {
    WARNING(DW_UNKNOWN_WARNING, "screen already open");
  }
  else
  {
    _screen = initscr();
    
    if (_screen != NULL)
    {
      [DAtExit add :self];
   
      if (has_colors())
      {
        start_color();
        
        _hasColors = YES;
        
        _attribute = COLOR_PAIR(0);
      }
      
      raw();
      noecho();
      keypad(stdscr, TRUE);
      meta(stdscr, TRUE);
      
      mousemask(ALL_MOUSE_EVENTS|REPORT_MOUSE_POSITION, &_mouseMask);
      
      ok = YES;
      
      [self drawable :COLS :LINES];
    }
  }
#endif
  
  if (ok)
  {
    _isOpen = YES;
    
    [self startDrawing];
    [self color :fgc :bgc];
    [self clear];
    [self stopDrawing];
  }
  
  return ok;
}

// 
// Close the text screen
// 
// @return the object
// 
- (DTextScreen *) close
{
  if (_isOpen)
  {
#ifdef WIN32
    if (_hOutput != INVALID_HANDLE_VALUE)
    {
      DWORD mode = (DWORD) _mouseMask;
      
      SetConsoleMode(_hInput, mode);
      
      _hOutput = INVALID_HANDLE_VALUE;
      _hInput  = INVALID_HANDLE_VALUE;
    }
#else
    if (_screen != NULL)
    {
      mousemask(_mouseMask, NULL);

      noraw();
      echo();
      endwin();
      
      _screen = NULL;
    }
#endif
    
    // reset drawable ??

    _isOpen    = NO;
    _hasColors = NO;
    
    [DAtExit remove :self];
  }
  
  return self;
}


//// Screen output methods

//
// Beep the terminal
//
// @return the object
//
- (DTextScreen *) beep 
{
#ifdef WIN32
  Beep(750, 250);
#else
  beep();
#endif
  
  return self;
}

//
// Set the visibility of the cursor
// 
// @param state   the cursor state (DDW_CURSOR..)
// 
// @return the previous state of the cursor
// 
- (int) cursor :(int) state
{
  int prev = 0;
  
#ifdef WIN32
  CONSOLE_CURSOR_INFO info;
  COORD               coord;
  
  coord.X = [self cursorX];
  coord.Y = [self cursorY];
  
  if (!SetConsoleCursorPosition(_hOutput, coord))
  {
    WARNING(DW_UNKNOWN_WARNING, "SetConsoleCursorPosition failed");
  }
  
  if (GetConsoleCursorInfo(_hOutput, &info))
  {
    if (info.bVisible)
    {
      prev = DDW_CURSOR_ON;
    }
    else
    {
      prev = DDW_CURSOR_OFF;
    }
    
    switch(state)
    {
     case DDW_CURSOR_OFF:
      info.bVisible = 0;
      info.dwSize   = 0;
      SetConsoleCursorInfo(_hOutput, &info);
      break;
      
     case DDW_CURSOR_ON:
      info.bVisible = 1;
      info.dwSize   = 100;
      SetConsoleCursorInfo(_hOutput, &info);
      break;
      
     default:
      WARNING(DW_INVALID_ARG, "state");
      break;
    }
  }
  else
  {
    WARNING(DW_UNKNOWN_WARNING, "GetConsoleCursorInfo failed");
  }
  
#else
  move([self cursorY], [self cursorX]);
  
  switch(state)
  {
   case DDW_CURSOR_OFF:
    prev = curs_set(0);
    break;
   case DDW_CURSOR_ON:
    prev = curs_set(1);
    break;
   default:
    WARNING(DW_INVALID_ARG, "state");
    break;
  }
  
  if ((prev == 1) || (prev == 2))
  {
    prev = DDW_CURSOR_ON;
  }
  else
  {
    prev = DDW_CURSOR_OFF;
  }
#endif

  return prev;
}

//
// Put a character on the screen (internal use)
//
// @param x       the x-position
// @param y       the y-position 
// @param ch      the character
// @param attr    the attributes
// 
// @return success
// 
- (BOOL) _putChar :(unsigned) x :(unsigned) y :(int) ch :(unsigned) attr
{
#ifdef WIN32
  COORD coord;
  TCHAR tch        = ch;
  WORD  attributes = (WORD) attr;
  DWORD written    = 0;
  
  coord.X = x;
  coord.Y = y;
  
  if (!WriteConsoleOutputCharacter(_hOutput, &tch, 1, coord, &written))
  {
    WARNING(DW_UNKNOWN_WARNING, "WriteConsoleOutputCharacter failed");
  }
  else if (!WriteConsoleOutputAttribute(_hOutput, &attributes, 1, coord, &written))
  {
    WARNING(DW_UNKNOWN_WARNING, "WriteConsoleOutputAttribute failed");
  }
#else
  mvaddch(y, x, ch|attr);
#endif

  return YES;
}

//
// Read a character from the screen (internal use)
// 
// @param x       the x-position
// @param y       the y-position
// @param ch      the read character
// @param attr    the read attributes
// 
// @return success
// 
- (BOOL) _getChar :(unsigned) x :(unsigned) y :(int *) ch :(unsigned *) attr
{
#ifdef WIN32
  COORD coord;
  TCHAR tch;
  WORD  attributes;
  DWORD written    = 0;
  BOOL  ok         = NO;
  
  coord.X = x;
  coord.Y = y;

  if (!ReadConsoleOutputCharacter(_hOutput, &tch, 1, coord, &written))
  {
    WARNING(DW_UNKNOWN_WARNING, "ReadConsoleOutputCharacter failed");
  }
  else if (!ReadConsoleOutputAttribute(_hOutput, &attributes, 1, coord, &written))
  {
    WARNING(DW_UNKNOWN_WARNING, "ReadConsoleOutputAttribute failed");
  }
  else
  {
    *ch   = (int)      tch;
    *attr = (unsigned) attributes; 
    
    ok = YES;
  }
  
  return ok;
#else
  int data = mvinch(y, x);
  
  *ch   = (int)      data & A_CHARTEXT;
  *attr = (unsigned) data & A_ATTRIBUTES;
    
  return YES;
#endif
}


//// Drawing control methods

//
// Start drawing on the screen
// 
// @return success
// 
- (BOOL) startDrawing
{
  BOOL ok = NO;
  
  if (!_isOpen)
  {
    WARNING(DW_INVALID_STATE, "open");
  }
  else
  {
    ok = [super startDrawing];
  }
  
  return ok;
}

//
// Stop drawing on the screen
// 
// @return the screen object
// 
- (DTextScreen *) stopDrawing
{
  if ([self isDrawing])
  {
    [super stopDrawing];

#ifndef WIN32
    refresh();
#endif
  }
  
  return self;
}


//// Event processing

#if _PRIVATE_

#ifdef WIN32
//
// Translate the win32 event
// 
// @param handler   the screen handler object
// @param obj       the key object
// @param event     the win32 event
//
// @return continue processing events ?
//  
static BOOL _translateEvent(DTextDrawable *self, id <DScreenHandler> handler, DKey *obj, INPUT_RECORD *event)
{
  BOOL continu = YES;
  int  key     = -1;
        
  switch(event->EventType)
  {
    case KEY_EVENT:
    {
      DWORD state = event->Event.KeyEvent.dwControlKeyState;
      BOOL  down  = (event->Event.KeyEvent.bKeyDown != 0);
      WORD  code  = event->Event.KeyEvent.wVirtualKeyCode;
      
      key   = (int) event->Event.KeyEvent.uChar.AsciiChar;

      if ((key > 0) && (key < 127) && (code != 8))
      {
        if ((state & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED)) != 0)
        {
          key |= DKEY_MOD_ALT;
        }
      }
      else 
      {
        if ((code >= VK_F1) && (code <= VK_F20))
        {
          key = DKEY_F1 + (code - VK_F1);
        }
        else
        {
          switch (code)
          {
            case VK_BACK      : key = DKEY_BACKSPACE; break;
            case VK_UP        : key = DKEY_UP;        break;
            case VK_DOWN      : key = DKEY_DOWN;      break;
            case VK_RIGHT     : key = DKEY_RIGHT;     break;
            case VK_LEFT      : key = DKEY_LEFT;      break;
            case VK_INSERT    : key = DKEY_INSERT;    break;
            case VK_DELETE    : key = DKEY_DELETE;    break;
            case VK_HOME      : key = DKEY_HOME;      break;
            case VK_END       : key = DKEY_END;       break;
            case VK_PRIOR     : key = DKEY_PAGEUP;    break;
            case VK_NEXT      : key = DKEY_PAGEDOWN;  break;
            default           : key = -1;
          }
        }
              
        if (key != -1)
        {
          if ((state & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED)) != 0)
          {
            key |= DKEY_MOD_ALT;
          }
          if ((state & (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED)) != 0)
          {
            key |= DKEY_MOD_CTRL;
          }
          if ((state & (CAPSLOCK_ON|SHIFT_PRESSED)) != 0)
          {
            key |= DKEY_MOD_SHIFT;
          }
        }
      }
            
      if ((key != -1) && (handler != nil))
      {
        [obj set :key];
      
        if (down)
        {
          continu &= [handler keyPress :obj :DSH_KEY_DOWN];
          continu &= [handler keyPress :obj :DSH_KEY_PRESS];
        }
        else
        {
          continu &= [handler keyPress :obj :DSH_KEY_UP];
        }
      }

      break;
    }

    case FOCUS_EVENT:
    {
      if (handler != nil)
      {
        continu &= [handler screenFocus :(event->Event.FocusEvent.bSetFocus != 0)];
      }
      break;
    }

    case MOUSE_EVENT:
    {
      DWORD flag    = event->Event.MouseEvent.dwEventFlags;
      DWORD control = event->Event.MouseEvent.dwControlKeyState;
      DWORD button  = event->Event.MouseEvent.dwButtonState;
      
      if ((flag & MOUSE_MOVED) != 0)
      {
        if (handler != nil)
        {
          unsigned x = event->Event.MouseEvent.dwMousePosition.X;
          unsigned y = event->Event.MouseEvent.dwMousePosition.Y;
                            
          continu &= [handler mouseMove :x :y];
        }
      }
      
      if ((flag == 0) || ((flag & DOUBLE_CLICK) != 0))
      {
        int state = (flag == 0 ? DSH_BUTTON_CLICK : DSH_BUTTON_DOUBLE_CLICK);

        key = -1;

        if ((button & FROM_LEFT_1ST_BUTTON_PRESSED) != 0)
        {
          key = DKEY_MOUSE_LEFT;
        }
        else if ((button & FROM_LEFT_2ND_BUTTON_PRESSED) != 0)
        {
          key = DKEY_MOUSE_MIDDLE;
        }
        else if ((button & RIGHTMOST_BUTTON_PRESSED) != 0)
        {
          key = DKEY_MOUSE_RIGHT;
        }

        if (key != -1)
        {
          if ((control & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED)) != 0)
          {
            key |= DKEY_MOD_ALT;
          }
          if ((control & (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED)) != 0)
          {
            key |= DKEY_MOD_CTRL;
          }
          if ((control & (CAPSLOCK_ON|SHIFT_PRESSED)) != 0)
          {
            key |= DKEY_MOD_SHIFT;
          }
        }

        if ((key != -1) && (handler != nil))
        {
          [obj set :key];
          
          continu &= [handler mouseButtonPress :obj :state];
        }
      }
      break;
    }
    
    case WINDOW_BUFFER_SIZE_EVENT:
    {
      int cols = event->Event.WindowBufferSizeEvent.dwSize.X;
      int rows = event->Event.WindowBufferSizeEvent.dwSize.Y;

      [self drawable :cols :rows];

      if (handler != nil)
      {
        continu &= [handler screenResize :[self maxX] :[self maxY]];
      }
      break;
    }
  }
  return continu;  
}

#else
//
// Translate the curses event
// 
// @param handler   the screen handler object
// @param obj       the key object
// @param key       the curses key
//
// @return continue processing events ?
//  
static BOOL _translateEvent(DTextDrawable *self, id <DScreenHandler> handler, DKey *obj, int key)
{
  BOOL continu = YES;
  
  if (key == KEY_MOUSE)
  {
    MEVENT event;
    
    if (getmouse(&event) == OK) // ToDo: only one event, previous state not present, rel+pres != click, click != rel+pres
    {
      int state = DSH_BUTTON_NONE;
      
      key   = 0;

      if (event.bstate & (BUTTON1_RELEASED|BUTTON1_PRESSED|BUTTON1_CLICKED|BUTTON1_DOUBLE_CLICKED|BUTTON1_TRIPLE_CLICKED))
      {
        key = DKEY_MOUSE_RIGHT;
      }
      else if (event.bstate & (BUTTON2_RELEASED|BUTTON2_PRESSED|BUTTON2_CLICKED|BUTTON2_DOUBLE_CLICKED|BUTTON2_TRIPLE_CLICKED))
      {
        key = DKEY_MOUSE_MIDDLE;
      }
      else if (event.bstate & (BUTTON3_RELEASED|BUTTON3_PRESSED|BUTTON3_CLICKED|BUTTON3_DOUBLE_CLICKED|BUTTON3_TRIPLE_CLICKED))
      {
        key = DKEY_MOUSE_LEFT;
      }
      
      if (key != 0)
      {
        if (event.bstate & (BUTTON1_RELEASED|BUTTON2_RELEASED|BUTTON3_RELEASED))
        {
          state = DSH_BUTTON_UP;
        }
        else if (event.bstate & (BUTTON1_PRESSED|BUTTON2_PRESSED|BUTTON3_PRESSED))
        {
          state = DSH_BUTTON_DOWN;
        }
        else if (event.bstate & (BUTTON1_CLICKED|BUTTON2_CLICKED|BUTTON3_CLICKED))
        {
          state = DSH_BUTTON_CLICK;
        }
        else if (event.bstate & (BUTTON1_DOUBLE_CLICKED|BUTTON2_DOUBLE_CLICKED|BUTTON3_DOUBLE_CLICKED))
        {
          state = DSH_BUTTON_DOUBLE_CLICK;
        }
        else if (event.bstate & (BUTTON1_TRIPLE_CLICKED|BUTTON2_TRIPLE_CLICKED|BUTTON3_TRIPLE_CLICKED))
        {
          state = DSH_BUTTON_TRIPLE_CLICK;
        }
        
        if (event.bstate & BUTTON_CTRL)
        {
          key |= DKEY_MOD_CTRL;
        }
        
        if (event.bstate & BUTTON_SHIFT)
        {
          key |= DKEY_MOD_SHIFT;
        }
        
        if (event.bstate & BUTTON_ALT)
        {
          key |= DKEY_MOD_ALT;
        }
      }
      
      if (handler != nil)
      {
        continu &= [handler mouseMove :event.x :event.y];
        
        if ((key != 0) && (state != DSH_BUTTON_NONE))
        {
          [obj set :key];
          
          continu &= [handler mouseButtonPress :obj :state];
        }
      }
    }
  }
  else
#ifdef KEY_RESIZE
    if (key == KEY_RESIZE)
    {
      [self drawable :COLS :LINES];
      
      continu &= [handler screenResize :[self maxX] :[self maxY]];
    }
  else
#endif
  {
    if ((key >= 0) && (key < 128))
    {
      // okee
    }
    else if ((key >= KEY_F(1)) && (key <= KEY_F(20)))
    {
      key = (DKEY_F1 + (key - KEY_F(1)));
    }
    else
    {
      switch (key)
      {
        case KEY_BACKSPACE: key = DKEY_BACKSPACE; break;
        case KEY_UP       : key = DKEY_UP;        break;
        case KEY_DOWN     : key = DKEY_DOWN;      break;
        case KEY_RIGHT    : key = DKEY_RIGHT;     break;
        case KEY_LEFT     : key = DKEY_LEFT;      break;
        case KEY_IC       : key = DKEY_INSERT;    break;
        case KEY_DC       : key = DKEY_DELETE;    break;
        case KEY_HOME     : key = DKEY_HOME;      break;
        case KEY_FIND     : key = DKEY_HOME;      break; // ??
        case KEY_END      : key = DKEY_END;       break;
        case KEY_SELECT   : key = DKEY_END;       break; // ??
        case KEY_PPAGE    : key = DKEY_PAGEUP;    break;
        case KEY_NPAGE    : key = DKEY_PAGEDOWN;  break;
        case KEY_SUSPEND  : key = 26;             break; // Ctrl-Z
        default           : key = -1; fprintf(stderr, "Unknown key: %d\n", key); break;
      }
    }
    
    if ((key != -1) && (handler != nil))
    {
      [obj set :key];
      
      continu &= [handler keyPress :obj :DSH_KEY_DOWN];
      continu &= [handler keyPress :obj :DSH_KEY_UP];
      continu &= [handler keyPress :obj :DSH_KEY_PRESS];
    }
  }
  
  return continu;
} 
#endif
#endif

//
// Wait (and process) events
// 
// @return the number of processed events
// 
- (unsigned) waitEvents
{
  unsigned processed = 0;
  
  if (_handler == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "screenHandler");
  }
  else
  {
    BOOL continu       = YES;

#ifndef WIN32
    BOOL lastKeyResize = NO;    // Ugly..
    
    nodelay(stdscr, FALSE);     // wait for keys..
#endif

    while (continu)
    {
#ifdef WIN32
      DWORD events = 0;
      INPUT_RECORD key; 

      if (!ReadConsoleInput(_hInput, &key, 1, &events))
      {
        WARNING(DW_UNKNOWN_WARNING, "ReadConsoleInput failed");
        events = 0;
      }
      
      if (events > 0)
      {
        continu = _translateEvent(self, _handler, _key, &key);

        processed++;
      }
#else
      int key = getch();

      if (key != ERR)
      {
        continu = _translateEvent(self, _handler, _key, key);

#ifdef KEY_RESIZE
        lastKeyResize = (key == KEY_RESIZE);
#endif
        processed++;
      }
      else if (!lastKeyResize)
      {
        continu = NO;
      }
      else
      {
        lastKeyResize = NO;
      }
#endif
    }
  }
  
  return processed;
}

//
// Process any pending events
//
// @return the number of processed events
// 
- (unsigned) processEvents
{
  unsigned processed = 0;

  if (_handler == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "screenHandler");
  }
  else
  {
    BOOL continu       = YES;

#ifndef WIN32
    BOOL lastKeyResize = NO;     // Ugly...
    
    nodelay(stdscr, TRUE);      // do not wait for keys..
#endif

    while (continu)
    {
#ifdef WIN32
      DWORD events = 0;
      INPUT_RECORD key; 
      
      if (!GetNumberOfConsoleInputEvents(_hInput, &events))
      {
        WARNING(DW_UNKNOWN_WARNING, "GetNumberOfConsoleInputEvents failed");
        events = 0;
      }
        
      if (events > 0)
      {
        events = 0;
        
        if (!ReadConsoleInput(_hInput, &key, 1, &events))
        {
          WARNING(DW_UNKNOWN_WARNING, "ReadConsoleInput failed");
          events = 0;
        }
      }
         
      if (events > 0)
      {
        continu = _translateEvent(self, _handler, _key, &key);

        processed++;
      }
      else
        continu = NO;
           
#else
      int key = getch();

      if (key != ERR)
      {
        continu = _translateEvent(self, _handler, _key, key);
        
#ifdef KEY_RESIZE
        lastKeyResize = (key == KEY_RESIZE);
#endif

        processed++;
      }
      else if (!lastKeyResize)
      {
        continu = NO;
      }
      else
      {
        lastKeyResize = YES;
      }
#endif
    }
  }

  return processed;
}

//
// Clear any pending events
//
// @return the object
// 
- (DTextScreen *) clearEvents
{
#ifdef WIN32
  if (!FlushConsoleInputBuffer(_hInput))
  {
    WARNING(DW_UNKNOWN_WARNING, "FlushConsoleInputBuffer failed");
  }    
#else
  int key;
  
  nodelay(stdscr, TRUE); 

  do
  {
    key = getch();
  }
  while (key != ERR);
#endif

  return self;
}

@end

#endif


  
  
  
#if _INTERFACE_

#ifdef HAVE_DTEXTSCREEN


#include "ofc/DIntArray.h"


//
// The DTextSurface class implements a class for off-screen drawing to a text
// surface. This surface can then be blit to the text screen.
// 

@interface DTextSurface : DTextDrawable
{
@private
  DIntArray           *_characters;     // the characters in the surface
  DIntArray           *_attributes;     // the attributes in the surface
}

#endif
#endif


#ifdef HAVE_DTEXTSCREEN


@implementation DTextSurface

//
// Initialise to an text surface object
//
// @return the object
//
- (DTextSurface *) init
{
  [super init];

  _characters = nil;
  _attributes = nil;
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of the text surface object
//
// @return the object
//
- shallowCopy
{
  DTextSurface *copy = [super shallowCopy];
  
  if (_characters != nil)
  {
    copy->_characters = [_characters copy];
  }
  if (_attributes != nil)
  {
    copy->_attributes = [_attributes copy];
  }
  
  return copy;
}


//// Deconstructor

//
// Free the text surface
// 
// @return the object
// 
- free
{
  [self close];
  
  return [super free];
}


//// Surface control methods

//
// Check if the surface is open
// 
// @return is it ?
// 
- (BOOL) isOpen
{
  return (_characters != nil);
}

//
// Open a text screen surface 
//
// @param columns   the number of columns 
// @param lines     the number of lines
// @param fgc       the initial foreground color
// @param bgc       the initial backgrounc color
// 
// @return success
// 
- (BOOL) open :(unsigned) columns :(unsigned) lines :(DColor *) fgc :(DColor *) bgc
{
  BOOL ok = NO;
  
#ifdef WIN32
  if (_hOutput == INVALID_HANDLE_VALUE)
#else
  if (_screen == NULL)
#endif
  {
    WARNING(DW_INVALID_STATE, "[DTextScreen open]");
  }
  else if (_characters != nil)
  {
    WARNING(DW_INVALID_STATE, "close");
  }
  else if (columns == 0)
  {
    WARNING(DW_INVALID_ARG, "columns");
  }
  else if (lines)
  {
    WARNING(DW_INVALID_ARG, "lines");
  }
  else
  {
    _characters = [DIntArray new];
    _attributes = [DIntArray new];
    
    [_characters size :columns * lines];
    [_attributes size :columns * lines];
       
    [self drawable :columns :lines];
    
    ok = [self startDrawing];
    [self color :fgc :bgc];
    [self clear];
    [self stopDrawing];
  }

  return ok;
}

//
// Close the surface
// 
// @return the object
// 
- (DTextSurface *) close
{
  if (_characters != nil)
  {
    [_characters free]; _characters = nil;
    [_attributes free]; _attributes = nil;
  }
  
  return self;
}


//// Private surface output methods

//
// Write a character on the screen (unclipped)
//
// @param x       the x-position
// @param y       the y-position 
// @param ch      the character
// @param attr    the attributes
// 
// @return success
// 
- (BOOL) _putChar :(unsigned) x :(unsigned) y :(int) ch :(unsigned) attr
{
  BOOL ok = YES;
  
  if (_characters != nil)
  {
    int offset = (x + y * [self columns]);
    
    [_characters put :offset :      ch  ];
    [_attributes put :offset :(int) attr];
  }
  else
  {
    WARNING(DW_INVALID_STATE, "open");
    
    ok = NO;
  }
  
  return ok;
}

//
// Read a character from the screen (internal use)
// 
// @param x       the x-position
// @param y       the y-position
// @param ch      the read character
// @param attr    the read attributes
// 
// @return success
// 
- (BOOL) _getChar :(unsigned) x :(unsigned) y :(int *) ch :(unsigned *) attr
{
  BOOL ok = NO;
  
  if (_characters != nil)
  {
    int offset = (x + y * [self columns]);
    
    *ch              = [_characters get :offset];
    *attr = (unsigned) [_attributes get :offset];
    
    ok = YES;
  }
  else
  {
    WARNING(DW_INVALID_STATE, "open");
  }
  
  return ok;
}

@end

#endif

/*===========================================================================*/
